<!-- 文件上传，基于 upload-file 和 upload-image 实现 -->
<template>
  <view class="uni-file-picker">
    <view v-if="title" class="uni-file-picker__header">
      <text class="file-title">{{ title }}</text>
      <text class="file-count">{{ filesList.length }}/{{ limitLength }}</text>
    </view>
    <view v-if="subtitle" class="file-subtitle">
      <view>{{ subtitle }}</view>
    </view>
    <upload-image
      v-if="fileMediatype === 'image' && showType === 'grid'"
      :readonly="readonly"
      :image-styles="imageStyles"
      :files-list="url"
      :limit="limitLength"
      :disablePreview="disablePreview"
      :delIcon="delIcon"
      @uploadFiles="uploadFiles"
      @choose="choose"
      @delFile="delFile"
    >
      <slot>
        <view class="is-add">
          <image :src="imgsrc" class="add-icon"></image>
        </view>
      </slot>
    </upload-image>
    <upload-file
      v-if="fileMediatype !== 'image' || showType !== 'grid'"
      :readonly="readonly"
      :list-styles="listStyles"
      :files-list="filesList"
      :showType="showType"
      :delIcon="delIcon"
      @uploadFiles="uploadFiles"
      @choose="choose"
      @delFile="delFile"
    >
      <slot><button type="primary" size="mini">选择文件</button></slot>
    </upload-file>
  </view>
</template>

<script>
import { defineComponent, PropType, ref, reactive, watch, onMounted, nextTick, getCurrentInstance } from 'vue';
import { chooseAndUploadFile, uploadCloudFiles } from './choose-and-upload-file';
import { get_extname, get_files_and_is_max, get_file_data } from './utils';
import uploadImage from './upload-image.vue';
import uploadFile from './upload-file.vue';
import sheep from '@/sheep';

export default defineComponent({
  name: 'sUploader',
  components: {
    uploadImage,
    uploadFile,
  },
  props: {
    modelValue: {
      type: [Array, Object] as PropType<Array<object> | object>,
      default: () => ([] as Array<object> | object),
    },
    url: {
      type: [Array, String] as PropType<Array<string> | string>,
      default: () => ([] as Array<string> | string),
    },
    disabled: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    disablePreview: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    delIcon: {
      type: Boolean as PropType<boolean>,
      default: true,
    },
    autoUpload: {
      type: Boolean as PropType<boolean>,
      default: true,
    },
    limit: {
      type: [Number, String] as PropType<number | string>,
      default: 9,
    },
    mode: {
      type: String as PropType<string>,
      default: 'grid',
    },
    fileMediatype: {
      type: String as PropType<string>,
      default: 'image',
    },
    fileExtname: {
      type: [Array, String] as PropType<Array<string> | string>,
      default: () => ([] as Array<string> | string),
    },
    title: {
      type: String as PropType<string>,
      default: '',
    },
    listStyles: {
      type: Object as PropType<object>,
      default: () => ({
        border: true,
        dividline: true,
        borderStyle: {},
      }),
    },
    imageStyles: {
      type: Object as PropType<object>,
      default: () => ({
        width: 'auto',
        height: 'auto',
      }),
    },
    readonly: {
      type: Boolean as PropType<boolean>,
      default: false,
    },
    sizeType: {
      type: Array as PropType<Array<string>>,
      default: () => (['original', 'compressed']),
    },
    driver: {
      type: String as PropType<string>,
      default: 'local',
    },
    subtitle: {
      type: String as PropType<string>,
      default: '',
    },
  },
  emits: [
    'select',
    'success',
    'fail',
    'progress',
    'delete',
    'update:modelValue',
    'update:url',
  ],
  setup(props, { emit }) {
    const files = reactive<Array<object>>([]);
    const localValue = ref<Array<object> | object>([]);
    const form = ref<any>(null);
    const formItem = ref<any>(null);
    let rename = '';
    let is_reset = false;

    onMounted(() => {
      if (props.driver === 'local') {
        uniCloud.chooseAndUploadFile = chooseAndUploadFile;
      }
      form.value = getForm('uniForms');
      formItem.value = getForm('uniFormsItem');
      if (form.value && formItem.value) {
        if (formItem.value.name) {
          rename = formItem.value.name;
          form.value.inputChildrens.push(this);
        }
      }
    });

    watch(() => props.modelValue, (newVal, oldVal) => {
      setValue(newVal, oldVal);
    }, { immediate: true });

    const setValue = async (newVal: any, oldVal: any) => {
      const newData = async (v: any) => {
        const reg = /cloud:\/\/([\w.]+\/?)\S*/;
        let url = '';
        if (v.fileID) {
          url = v.fileID;
        } else {
          url = v.url;
        }
        if (reg.test(url)) {
          v.fileID = url;
          v.url = await getTempFileURL(url);
        }
        if (v.url) v.path = v.url;
        return v;
      };
      if (props.returnType === 'object') {
        if (newVal) {
          await newData(newVal);
        } else {
          newVal = {};
        }
      } else {
        if (!newVal) newVal = [];
        for (let i = 0; i < newVal.length; i++) {
          let v = newVal[i];
          await newData(v);
        }
      }
      localValue.value = newVal;
      if (form.value && formItem.value && !is_reset) {
        is_reset = false;
        formItem.value.setValue(localValue.value);
      }
      let filesData = Object.keys(newVal).length > 0 ? newVal : [];
      files.splice(0, files.length, ...filesData);
    };

    const choose = () => {
      if (props.disabled) return;
      if (
        files.length >= Number(props.limitLength) &&
        props.mode !== 'grid' &&
        props.returnType === 'array'
      ) {
        uni.showToast({
          title: `您最多选择 ${props.limitLength} 个文件`,
          icon: 'none',
        });
        return;
      }
      chooseFiles();
    };

    const chooseFiles = () => {
      const _extname = get_extname(props.fileExtname);
      // 获取后缀
      uniCloud
        .chooseAndUploadFile({
          type: props.fileMediatype,
          compressed: false,
          sizeType: props.sizeType,
          // TODO: 如果为空，video 有问题
          extension: _extname.length > 0 ? _extname : undefined,
          count: props.limitLength - files.length, //默认9
          onChooseFile: chooseFileCallback,
          onUploadProgress: (progressEvent: any) => {
            setProgress(progressEvent, progressEvent.index);
          },
        })
        .then((result) => {
          setSuccessAndError(result.tempFiles);
        })
        .catch((err) => {
          console.log('选择失败', err);
        });
    };

    const chooseFileCallback = async (res: any) => {
      const _extname = get_extname(props.fileExtname);
      const is_one =
        (Number(props.limitLength) === 1 && props.disablePreview && !props.disabled) ||
        props.returnType === 'object';
      // 如果这有一个文件 ，需要清空本地缓存数据
      if (is_one) {
        files.splice(0, files.length);
      }

      let { filePaths, files } = get_files_and_is_max(res, _extname);
      if (!(_extname && _extname.length > 0)) {
        filePaths = res.tempFilePaths;
        files = res.tempFiles;
      }

      let currentData = [];
      for (let i = 0; i < files.length; i++) {
        if (props.limitLength - files.length <= 0) break;
        files[i].uuid = Date.now();
        let filedata = await get_file_data(files[i], props.fileMediatype);
        filedata.progress = 0;
        filedata.status = 'ready';
        files.push(filedata);
        currentData.push({
          ...filedata,
          file: files[i],
        });
      }
      emit('select', {
        tempFiles: currentData,
        tempFilePaths: filePaths,
      });
      res.tempFiles = files;
      // 停止自动上传
      if (!props.autoUpload) {
        res.tempFiles = [];
      }
    };

    const uploadFiles = (files: any[]) => {
      files = [...files];
      return uploadCloudFiles
        .call(this, files, 5, (res: any) => {
          setProgress(res, res.index, true);
        })
        .then((result) => {
          setSuccessAndError(result);
          return result;
        })
        .catch((err) => {
          console.log(err);
        });
    };

    const setSuccessAndError = async (res: any, fn?: any) => {
      let successData = [];
      let errorData = [];
      let tempFilePath = [];
      let errorTempFilePath = [];
      for (let i = 0; i < res.length; i++) {
        const item = res[i];
        const index = item.uuid ? files.findIndex((p) => p.uuid === item.uuid) : item.index;

        if (index === -1 || !files) break;
        if (item.errMsg === 'request:fail') {
          files[index].url = item.path;
          files[index].status = 'error';
          files[index].errMsg = item.errMsg;
          // files[index].progress = -1
          errorData.push(files[index]);
          errorTempFilePath.push(files[index].url);
        } else {
          files[index].errMsg = '';
          files[index].fileID = item.url;
          const reg = /cloud:\/\/([\w.]+\/?)\S*/;
          if (reg.test(item.url)) {
            files[index].url = await getTempFileURL(item.url);
          } else {
            files[index].url = item.url;
          }

          files[index].status = 'success';
          files[index].progress += 1;
          successData.push(files[index]);
          tempFilePath.push(files[index].fileID);
        }
      }

      if (successData.length > 0) {
        setEmit();
        // 状态改变返回
        emit('success', {
          tempFiles: backObject(successData),
          tempFilePaths: tempFilePath,
        });
      }

      if (errorData.length > 0) {
        emit('fail', {
          tempFiles: backObject(errorData),
          tempFilePaths: errorTempFilePath,
        });
      }
    };

    const setProgress = (progressEvent: any, index: number, type?: boolean) => {
      const fileLenth = files.length;
      const percentNum = (index / fileLenth) * 100;
      const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
      let idx = index;
      if (!type) {
        idx = files.findIndex((p) => p.uuid === progressEvent.tempFile.uuid);
      }
      if (idx === -1 || !files[idx]) return;
      // fix by mehaotian 100 就会消失，-1 是为了让进度条消失
      files[idx].progress = percentCompleted - 1;
      // 上传中
      emit('progress', {
        index: idx,
        progress: parseInt(percentCompleted),
        tempFile: files[idx],
      });
    };

    const delFile = (index: number) => {
      emit('delete', {
        tempFile: files[index],
        tempFilePath: files[index].url,
      });
      files.splice(index, 1);
      nextTick(() => {
        setEmit();
      });
    };

    const getFileExt = (name: string) => {
      const last_len = name.lastIndexOf('.');
      const len = name.length;
      return {
        name: name.substring(0, last_len),
        ext: name.substring(last_len + 1, len),
      };
    };

    const setEmit = () => {
      let data = [];
      let updateUrl = [];
      if (props.returnType === 'object') {
        data = backObject(files)[0];
        localValue.value = data ? data : null;
        updateUrl = data ? data.url : '';
      } else {
        data = backObject(files);
        if (!localValue.value) {
          localValue.value = [];
        }
        localValue.value = [...data];
        if (localValue.value.length > 0) {
          localValue.value.forEach((item) => {
            updateUrl.push(item.url);
          });
        }
      }
      emit('update:modelValue', localValue.value);
      emit('update:url', updateUrl);
    };

    const backObject = (files: Array<object>) => {
      let newFilesData = [];
      files.forEach((v) => {
        newFilesData.push({
          extname: v.extname,
          fileType: v.fileType,
          image: v.image,
          name: v.name,
          path: v.path,
          size: v.size,
          fileID: v.fileID,
          url: v.url,
        });
      });
      return newFilesData;
    };

    const getTempFileURL = async (fileList: any) => {
      fileList = {
        fileList: [].concat(fileList),
      };
      const urls = await uniCloud.getTempFileURL(fileList);
      return urls.fileList[0].tempFileURL || '';
    };

    const getForm = (name: string = 'uniForms') => {
      let parent = getCurrentInstance()?.parent;
      let parentName = parent?.name;
      while (parentName !== name) {
        parent = parent?.$parent;
        if (!parent) return false;
        parentName = parent?.name;
      }
      return parent;
    };

    return {
      files,
      localValue,
      form,
      formItem,
      rename,
      is_reset,
      setValue,
      choose,
      delFile,
      getFileExt,
    };
  },
});

</script>

<style lang="scss" scoped>
  .uni-file-picker {
    /* #ifndef APP-NVUE */
    box-sizing: border-box;
    overflow: hidden;
    /* width: 100%; */
    /* #endif */
    /* flex: 1; */
    position: relative;
  }

  .uni-file-picker__header {
    padding-top: 5px;
    padding-bottom: 10px;
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    justify-content: space-between;
  }

  .file-title {
    font-size: 14px;
    color: #333;
  }

  .file-count {
    font-size: 14px;
    color: #999;
  }

  .is-add {
    /* #ifndef APP-NVUE */
    display: flex;
    /* #endif */
    align-items: center;
    justify-content: center;
  }
  .add-icon {
    width: 57rpx;
    height: 49rpx;
  }
  .file-subtitle {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 0;
    width: 140rpx;
    height: 36rpx;
    z-index: 1;
    display: flex;
    justify-content: center;
    color: #fff;
    font-weight: 500;
    background: rgba(#000, 0.3);
    font-size: 24rpx;
  }
</style>
